package lar.system.network;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.channels.spi.SelectorProvider;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.util.Log;

import lar.system.engine.MainProgram;
import lar.system.network.msgParser.Message;
import lar.system.network.msgParser.ParsedMessage;
import lar.system.network.msgParser.Strings;


public class TCPListener extends Listener implements Runnable
{

	private Selector            		selector;
    private ServerSocketChannel 		sChan;
    private Vector<SocketChannel> 		sockets;
    private MessageTokenizer<String> 	tokenizer;
    private LinkedList<SocketChannel>   toRegister;
    boolean shouldRun = true;
    
    public Vector<SocketChannel> getSockets()
    {
    	return sockets;
    }
    
    public static byte[] getWifiAddressBytes()
    {
	    int ip = getWifiAddressInt();
	    return WifiAddressIntToByte(ip);
    }
    
    public static byte[] WifiAddressIntToByte(int ip)
    {
    	byte[] ans = new byte[4];
	    ans[3] = (byte)((ip >> 24)&0xFF);
	    ans[2] = (byte)((ip >> 16)&0xFF);
	    ans[1] = (byte)((ip >> 8)&0xFF);
	    ans[0] = (byte)(ip&0xFF);
	   // for(int i = 0; i < 4; i++) Log.d("R", ""+ (int)ans[i]);
	    return ans;
    }
    
    public static int WifiAddressByteToInt(byte[] addr)
    {
    	return java.nio.ByteBuffer.wrap(addr).order(java.nio.ByteOrder.LITTLE_ENDIAN).getInt();
    }
    
    public static int getWifiAddressInt()
    {
	    WifiManager wifiMgr = (WifiManager)MainProgram.currentActivity.getSystemService(Context.WIFI_SERVICE);
	    WifiInfo wifiInfo = wifiMgr.getConnectionInfo();
	    int ip = wifiInfo.getIpAddress();
	    return ip;
    }
    
    public synchronized void shutDown()
    {
    	shouldRun = false;
        	try 
			{
    			for(SocketChannel sc : sockets) if(sc.isConnected() || sc.isOpen()) sc.close();
    			//selector.close();
				sChan.close();
			} 
			catch (Exception e) 
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			selector.wakeup();
	   }
    
    public void registerSocketChannel(SocketChannel sc)
    {
    	synchronized(toRegister)
    	{
    		toRegister.add(sc);
    	}
    	selector.wakeup();
    	
    }
    
	public void run() 
	{
        try 
        {
        	toRegister = new LinkedList<SocketChannel>();
        	tokenizer = new FixedSeparatorMessageTokenizer(Strings.FINALIZER, Charset.forName("UTF-8"));
            selector = SelectorProvider.provider().openSelector();
            sChan = ServerSocketChannel.open();
            //Log.d("R", ""+ InetAddress.getLocalHost().toString());
            InetSocketAddress iaddr = new InetSocketAddress(InetAddress.getByAddress(getWifiAddressBytes()), 8001);

            sChan.configureBlocking(false);
            sChan.socket().bind(iaddr);

            sChan.register(selector, SelectionKey.OP_ACCEPT);
            sockets = new Vector<SocketChannel>();
            Log.d("R", "Listener started");
        }
        catch (Exception e) 
        {
        	Log.d("R", "ERROR1 " + e.getMessage());// e.printStackTrace();
        }

        Iterator<SelectionKey> it;

        try {
            while (shouldRun && selector.isOpen()) 
            {
            	Log.d("R", "Listener running");
            	synchronized(toRegister)
            	{
            		for(SocketChannel sc : toRegister) 
            		{
            			sockets.add(sc);
            			sc.register(selector, SelectionKey.OP_READ);
            		}
            		toRegister.clear();
            	}
            	selector.select();
                
                it = selector.selectedKeys().iterator();
                while (it.hasNext()) 
                {
                    SelectionKey key = it.next();

                    it.remove();
                    if (!key.isValid()) 
                    {
                    	Log.d("R", "Listener: Invalid key");
                        continue;
                    }

                    // Finish connection in case of an error
                    if (key.isConnectable()) 
                    {
                    	Log.d("R", "Listener: Connectable");
                        SocketChannel ssc = (SocketChannel) key.channel();
                        if (ssc.isConnectionPending()) ssc.finishConnect();
                    }

                    if (key.isAcceptable()) 
                    {
                        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                        SocketChannel newClient = ssc.accept();

                        newClient.configureBlocking(false);
                        newClient.register(selector, SelectionKey.OP_READ);
                        sockets.add(newClient);
                        Log.d("R", "Listener: Connection");
                        //System.out.println("Connection from " + newClient.socket().getInetAddress().getHostAddress());
                    }

                    if (key.isReadable()) 
                    {
                        SocketChannel sc = (SocketChannel) key.channel();
                        ByteBuffer data = ByteBuffer.allocate(sc.socket().getSendBufferSize());

                        //System.out.println("Data from " + sc.socket().getInetAddress().getHostAddress());
                        Log.d("R", "Listener: Read");
                        if (sc.read(data) == -1) //is this a socket close?
                        {
                        	Log.d("R", "Listener: Socket close");
                        	ParsedMessage pm = new ParsedMessage(Message.createMessage(Strings.GOOD_BYE));
                        	pm.Parse();
                        	pm.setAttachment(sc);
                        	key.cancel();
                        	sc.close();
                        	sockets.remove(sc);
                        	synchronized(Q) { Q.add(pm); }
                            continue;
                        }

                        data.flip();
                        tokenizer.addBytes(data);
                        while(tokenizer.hasMessage())
                        {
                        	ParsedMessage pm = new ParsedMessage(tokenizer.nextMessage());
                        	pm.Parse();
                        	pm.setAttachment(sc);
                        	synchronized(Q) { Q.add(pm); }
                        }
                        //sc.close();
                    }
                }
            }
        }
        catch (Exception e) 
        {
           e.printStackTrace();
        }
    }

}
